package org.richin.support.orm.hibernate;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

import org.apache.commons.lang.StringUtils;
import org.hibernate.Criteria;
import org.hibernate.Query;
import org.hibernate.SessionFactory;
import org.hibernate.criterion.CriteriaSpecification;
import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.Disjunction;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Projection;
import org.hibernate.criterion.Projections;
import org.hibernate.criterion.Restrictions;
import org.hibernate.impl.CriteriaImpl;
import org.hibernate.transform.ResultTransformer;
import org.richin.support.orm.IPageDAO;
import org.richin.support.orm.Page;
import org.springframework.util.Assert;
import org.springside.modules.orm.PropertyFilter;
import org.springside.modules.utils.ReflectionUtils;

@SuppressWarnings("unchecked")
public class PageHibernateDao<T, ID extends Serializable> extends
		HibernateDaoSupportImp<T, ID> implements IPageDAO<T, ID>{
	public PageHibernateDao() {
	}

	public PageHibernateDao(SessionFactory sessionFactory, Class<T> entityClass) {
		super(sessionFactory, entityClass);
	}

	public Page<T> getAll(Page<T> page) {
		return findByCriteria(page, new Criterion[0]);
	}

	public Page<T> find(Page<T> page, String hql, Object...values) {
		Assert.notNull(page);

		Query q = createQuery(hql, values);
		setPageParameter(q, page);
		List<T> result = q.list();
		page.setResult(result);
		return page;
	}

	public Page<T> findByCriteria(Page<T> page, Criterion...criterions) {
		Assert.notNull(page);

		Criteria c = createCriteria(criterions);

		if (page.isAutoCount()) {
			int totalCount = countCriteriaResult(c, page);
			page.setTotalCount(totalCount);
		}

		setPageParameter(c, page);
		List result = c.list();
		page.setResult(result);
		return page;
	}

	protected Query setPageParameter(Query q, Page<T> page) {
		q.setFirstResult(page.getFirst());
		q.setMaxResults(page.getPageSize());
		return q;
	}

	protected Criteria setPageParameter(Criteria c, Page<T> page) {
		String[] orderByArray;
		String[] orderArray;
		int i;
		c.setFirstResult(page.getFirst());
		c.setMaxResults(page.getPageSize());

		if (page.isOrderBySetted()) {
			orderByArray = StringUtils.split(page.getOrderBy(), ',');
			orderArray = StringUtils.split(page.getOrder(), ',');

			Assert.isTrue(orderByArray.length == orderArray.length,
					"分页多重排序参数中,排序字段与排序方向的个数不相等");

			for (i = 0; i < orderByArray.length; ++i)
				if ("asc".equals(orderArray[i]))
					c.addOrder(Order.asc(orderByArray[i]));
				else
					c.addOrder(Order.desc(orderByArray[i]));

		}

		return c;
	}

	protected int countCriteriaResult(Criteria c, Page<T> page) {
		CriteriaImpl impl = (CriteriaImpl) c;

		Projection projection = impl.getProjection();
		ResultTransformer transformer = impl.getResultTransformer();

		List orderEntries = null;
		try {
			orderEntries = (List) ReflectionUtils.getFieldValue(impl,
					"orderEntries");
			ReflectionUtils
					.setFieldValue(impl, "orderEntries", new ArrayList());
		} catch (Exception e) {
			this.logger.error("不可能抛出的异常:{}", e.getMessage());
		}

		int totalCount = ((Integer) c.setProjection(Projections.rowCount())
				.uniqueResult()).intValue();

		c.setProjection(projection);

		if (projection == null)
			c.setResultTransformer(CriteriaSpecification.ROOT_ENTITY);

		if (transformer != null)
			c.setResultTransformer(transformer);
		try {
			ReflectionUtils.setFieldValue(impl, "orderEntries", orderEntries);
		} catch (Exception e) {
			this.logger.error("不可能抛出的异常:{}", e.getMessage());
		}

		return totalCount;
	}

	public List<T> findByProperty(String propertyName, Object value,
			String matchTypeStr) {
		PropertyFilter.MatchType matchType = (PropertyFilter.MatchType) Enum
				.valueOf(PropertyFilter.MatchType.class, matchTypeStr);
		Criterion criterion = buildPropertyCriterion(propertyName, value,
				matchType);
		return findByCriteria(new Criterion[] { criterion });
	}

	public List<T> findByFilters(List<PropertyFilter> filters) {
		Criterion[] criterions = buildPropertyFilterCriterions(filters);
		return findByCriteria(criterions);
	}

	public Page<T> findByFilters(Page<T> page, List<PropertyFilter> filters) {
		Criterion[] criterions = buildPropertyFilterCriterions(filters);
		return findByCriteria(page, criterions);
	}

	protected Criterion[] buildPropertyFilterCriterions(
			List<PropertyFilter> filters) {
		List criterionList = new ArrayList();
		for (PropertyFilter filter : filters) {
			String propertyName = filter.getPropertyName();

			boolean multiProperty = StringUtils.contains(propertyName, "|");
			if (!(multiProperty)) {
				Criterion criterion = buildPropertyCriterion(propertyName,
						filter.getValue(), filter.getMatchType());
				criterionList.add(criterion);
			} else {
				Disjunction disjunction = Restrictions.disjunction();
				String[] params = StringUtils.split(propertyName, '|');

				String[] arr$ = params;
				int len$ = arr$.length;
				for (int i$ = 0; i$ < len$; ++i$) {
					String param = arr$[i$];
					Criterion criterion = buildPropertyCriterion(param, filter
							.getValue(), filter.getMatchType());
					disjunction.add(criterion);
				}
				criterionList.add(disjunction);
			}
		}
		return ((Criterion[]) criterionList.toArray(new Criterion[criterionList
				.size()]));
	}



	public Page<T> find(Page<T> page, List<PropertyFilter> filters) {
		// TODO Auto-generated method stub
		return null;
	}

	public Page<T> findByNamedQuery(Page<T> page, String queryName, Object... values) {
		Assert.notNull(page);
		Query query = getNamedQuery(queryName, values);
		setPageParameter(query, page);
		List<T> result = query.list();
		page.setResult(result);
		return page;
	}

}
